# # REST in Place Editor #####################################################
#
# This is the main class that manages the manipulation of the DOM and the AJAX
# requests
class RestInPlaceEditor
  constructor : (e) ->
    @$element = $(e)
    @initOptions()
    @bindForm()
    @createClickHandler()

    @$element.click(@clickHandler)

    @updateDisplayValue(@$element.text())

  # ## Public interface functions ############################################

  # Toggle the element associated with this editor from normal to a form field
  activate : ->
    @oldValue = @elementHTML()
    @$element.trigger('activate.rest-in-place')
    @$element.addClass('rip-active')
    @$element.unbind('click', @clickHandler)
    @activateForm()
    @$element.trigger('ready.rest-in-place')

  # Restore the element to its default state
  abort : ->
    @$element.trigger('abort.rest-in-place')
    @updateDisplayValue(@oldValue)
    @$element.removeClass('rip-active')
      .click(@clickHandler)

  # Take the changes a user has made and send them to the server.
  # If the server accepted the request but does not send back a
  # parseable answer, a second request is initiated to retrieve the updated
  # value via GET
  update : ->
    @$element.trigger('update.rest-in-place')
    @newValue = @getValue()
    updateRequest = @ajax
      type : "post"
      data : @requestData()

    updateRequest.done (data, textStatus, jqXHR) =>
      if data
        @loadSuccessCallback(data)
      else if (jqXHR.status == 204)
        @loadSuccessCallback(@newValue, true)
      else
        @loadViaGET()

    updateRequest.fail (jqXHR, textStatus) =>
      if (jqXHR.status == 200 && textStatus == "parsererror")
        @loadViaGET()
      else
        @$element.trigger('failure.rest-in-place', jqXHR.responseJSON)
        @abort()

    @$element.html("saving...")

  loadViaGET : ->
    showRequest = @ajax()
    showRequest.done (data) => @loadSuccessCallback(data)
    showRequest.fail => @$element.trigger('failure.rest-in-place', showRequest.responseJSON)

  # ## Implementation Methods
  #
  # These are not implemented in RestInPlaceEditor. Instead, different form
  # types implement this method (and some others). See [Forms](#forms)

  # Turns the elements HTML into a form.
  activateForm : ->
    alert("The form was not properly initialized. activateForm is unbound")

  # When the element is active (it is a form), thtis method returns the value
  # that should be sent to the server.
  getValue : ->
    alert("The form was not properly initialized. getValue is unbound")

  # ## Helper Functions ######################################################

  # Derives configuration options from different sources:
  #
  # 1. Look through the chain of parent elements and search for data attributes
  # 2. Try to guess the object name based on Rails id naming conventions if
  #    parents did not explicitly supply something
  # 3. Take own data attributes, these always override other settings.
  #
  # These are the options:
  #
  # **data-url / @url**
  # Where to send/receive data from
  #
  # **data-formtype / @formType**
  # Which form extension to use (see [Forms](#forms))
  #
  # **data-object / @objectName**
  # Rails singular lowercase name of the class of the objects. This is used
  # to generate requests/parse responses that ActionController can understand.
  #
  # **data-attributes / @attributeName**
  # Name of the attribute on the object. Combined with the object name to build
  # query string parameters in the form `object[attribute]`, just as Rails
  # expects it.
  initOptions : ->
    @$element.parents().each (index, parent) =>
      @url           = @url           || $(parent).attr("data-url")
      @formType      = @formType      || $(parent).attr("data-formtype")
      @objectName    = @objectName    || $(parent).attr("data-object")
      @attributeName = @attributeName || $(parent).attr("data-attribute")
      @placeholder   = @placeholder   || $(parent).attr("data-placeholder")

    @$element.parents().each (index, parent) =>
      @objectName = @objectName || res[1] if res = parent.id.match(/^(\w+)_(\d+)$/i)

    @url           = @$element.attr("data-url")         || @url         || document.location.pathname
    @formType      = @$element.attr("data-formtype")    || @formType    || "input"
    @objectName    = @$element.attr("data-object")      || @objectName
    @attributeName = @$element.attr("data-attribute")   || @attributeName
    @placeholder   = @$element.attr("data-placeholder") || @placeholder || ''

  # Overwrites formtype specific method implementations during initialization
  bindForm : ->
    @activateForm = RestInPlaceEditor.forms[@formType].activateForm
    @getValue     = RestInPlaceEditor.forms[@formType].getValue

  # Generate the data that is sent in the POST request
  requestData : ->
    data = @getEncodedTokenAuthenticationParams()
    data["_method"] = 'put'
    value = @getValue()
    if value != @placeholder
      data["#{@objectName}[#{@attributeName}]"] = @getValue()
    data

  # Extract CSRF token from metatags
  getEncodedTokenAuthenticationParams : ->
    data = {}
    param = $('meta[name=csrf-param]').attr('content')
    token = $('meta[name=csrf-token]').attr('content')
    data[param] = token if param && token
    data

  # A wrapper for jQuery.ajax
  ajax : (options = {}) ->
    options.url      = @url
    options.dataType = "json"
    $.ajax(options)

  # Extract the actual attribute value from the servers response
  extractAttributeFromData : (data) ->
    if @include_root_in_json
      data[@objectName][@attributeName]
    else
      data[@attributeName]

  updateDisplayValue : (value) ->
    if $.trim(value).length < 1
      @$element.html(@placeholderHTML())
    else
      @$element.text(value)

  placeholderHTML : ->
    """<span class="rest-in-placeholder">#{@placeholder}</span>"""

  elementHTML: ->
    value = @$element.text()
    value = '' if value  == @placeholderHTML()
    value

  # ## Handlers ##############################################################

  # Handles the successful response from the server
  loadSuccessCallback : (data, nocontent) ->
    if nocontent
      @updateDisplayValue(data)
    else
      @updateDisplayValue(@extractAttributeFromData(data))
    @$element.click(@clickHandler)
    @$element.removeClass('rip-active')
    @$element.trigger('success.rest-in-place', data)


  # Creates a clickhandler for the current instance of RestInPlaceEditor, that
  # has its this pointer permanently bound to the editor instance.
  createClickHandler : ->
    @clickHandler = (event) => @activate()

# # <a name="forms">Forms</forms>
#
# Contains form type implementations. A form type implementation needs to
# provide two methods, `activateForm` and `getValue`, which are transplanted
# into the RestInPlaceEditor instance.
#
# `activateForm` : Turns the elements HTML into a form.
# `getValue` : When the element is active (it is a form), this method returns
#              the value that should be sent to the server.
RestInPlaceEditor.forms =
  "input" :
    activateForm : ->
      value = $.trim(@elementHTML())
      @$element.html("""<form action="javascript:void(0)" style="display:inline;">
        <input type="text" class="rest-in-place-#{@attributeName}" placeholder="#{@placeholder}">
        </form>""")
      @$element.find('input').val(value)
      @$element.find('input')[0].select()
      @$element.find("form").submit =>
        @update()
        false
      @$element.find("input").keyup (e) =>
        @abort() if e.keyCode == ESC_KEY
      @$element.find("input").blur => @abort()

    getValue : ->
      @$element.find("input").val()

  "textarea" :
    activateForm : ->
      value = $.trim(@elementHTML())
      @$element.html("""<form action="javascript:void(0)" style="display:inline;">
        <textarea class="rest-in-place-#{@attributeName}" placeholder="#{@placeholder}"></textarea>
        </form>""")
      @$element.find('textarea').val(value)
      @$element.find('textarea')[0].select()
      @$element.find("textarea").keyup (e) =>
        @abort() if e.keyCode == ESC_KEY
      @$element.find("textarea").blur => @update()

    getValue : ->
      @$element.find("textarea").val()

# ------------------
#
# ### Support functionality

# Symbolic representation of the ESC Keycode
ESC_KEY = 27

# Assign jQuery shortcut
$ = jQuery

# Detect Rails Settings
RestInPlaceEditor.prototype.include_root_in_json = "<%= RestInPlace.include_root_in_json? %>" == "true"

# Install in global namespace
window.RestInPlaceEditor = RestInPlaceEditor

# Create jQuery function
# Use this to setup REST in Place functionality for elements of your page,
# for example:
#
#     jQuery(".rest-in-place").restInPlace();
$.fn.restInPlace = ->
  @each ->
    $(this).data('restInPlaceEditor', new RestInPlaceEditor(this))
  return this


# Run automatically
$(document).on 'ready page:load', -> $('.rest-in-place').restInPlace()
